/*
 * This file is part of the Code::Blocks IDE and licensed under the GNU General Public License, version 3
 * http://www.gnu.org/licenses/gpl-3.0.html
 *
 * $Revision: 10771 $
 * $Id: envvars_common.cpp 10771 2016-02-06 14:29:31Z mortenmacfly $
 * $HeadURL: http://svn.code.sf.net/p/codeblocks/code/branches/release-17.xx/src/plugins/contrib/envvars/envvars_common.cpp $
 */

// ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- -----

#include "sdk.h"
#include "prep.h"
#ifndef CB_PRECOMP
  #include <wx/checklst.h>
  #include <wx/utils.h>

  #include "configmanager.h"
  #include "globals.h"
  #include "manager.h"
  #include "macrosmanager.h"
  #include "logmanager.h"
#endif

#include <map>
#include <utility> // std::pair

#include "envvars_common.h"

// Uncomment this for tracing of method calls in C::B's DebugLog:
// 取消注释此项以跟踪C::B的调试日志中的方法调用:
//#define TRACE_ENVVARS

// ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- -----

namespace nsEnvVars
{
  const wxString              EnvVarsSep     = _T("|");
  const wxString              EnvVarsDefault = _T("default");
  std::map<wxString,wxString> EnvVarsStack;
}

// ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- -----

void nsEnvVars::EnvVarsDebugLog(const wxChar* msg, ...)
{
  // load and apply configuration (to application only)
  // 加载和应用配置(仅适用于应用程序)
  ConfigManager *cfg = Manager::Get()->GetConfigManager(_T("envvars"));
  if (!cfg)
    return;

  // get whether to print debug message to debug log or not
  // 获取是否将调试消息打印到调试日志
  bool debug_log = cfg->ReadBool(_T("/debug_log"));
  if (!debug_log)
    return;

  wxString log_msg;
  va_list  arg_list;

  va_start(arg_list, msg);
#if wxCHECK_VERSION(3, 0, 0) && wxUSE_UNICODE
// in wx >=  3 unicode-build (default) we need the %ls here, or the strings get
// cut after the first character
// 在wx>=3 unicode构建(默认)中,这里需要%ls,否则字符串将在第一个字符之后被剪切
    log_msg = msg;
    log_msg.Replace(_T("%s"), _T("%ls"));
    msg = log_msg.wx_str();
#endif

  log_msg = wxString::FormatV(msg, arg_list);
  va_end(arg_list);

  Manager::Get()->GetLogManager()->DebugLog(wxT("nsEnvVars::EnvVarsDebugLog:")+log_msg);
}// EnvVarsDebugLog

// ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- -----

wxArrayString nsEnvVars::EnvvarStringTokeniser(const wxString& str)
{
#if defined(TRACE_ENVVARS)
  Manager::Get()->GetLogManager()->DebugLog(F(_T("EnvvarStringTokeniser")));
#endif
  // 标记字符串如下:
  // tokenise string like:
  // C:\Path;"D:\Other Path"

  wxArrayString out;

  wxString search = str;
  search.Trim(true).Trim(false);

  // trivial case: string is empty or consists of blanks only
  // 普通情况:字符串为空或仅由空格组成
  if (search.IsEmpty())
    return out;

  wxString token;
  bool     inside_quot = false;
  size_t   pos         = 0;
  while (pos < search.Length())
  {
    wxString current_char = search.GetChar(pos);

    // for e.g. /libpath:"C:\My Folder"
    // 例如  /libpath:"C:\My Folder"
    // equality 相等
    if (current_char.CompareTo(_T("\""))==0)
      inside_quot = !inside_quot;

    if ((current_char.CompareTo(nsEnvVars::EnvVarsSep)==0) && (!inside_quot))
    {
      if (!token.IsEmpty())
      {
        out.Add(token);
        token.Clear();
      }
    }
    else
      token.Append(current_char);

    pos++;
    // Append final token
    // 附加最终标记
    if ((pos==search.Length()) && (!inside_quot) && (!token.IsEmpty()))
      out.Add(token);
  }// while

  return out;
}// EnvvarStringTokeniser

// ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- -----

wxArrayString nsEnvVars::GetEnvvarSetNames()
{
#if defined(TRACE_ENVVARS)
  Manager::Get()->GetLogManager()->DebugLog(F(_T("GetEnvvarSetNames")));
#endif

  wxArrayString set_names;

  ConfigManager *cfg = Manager::Get()->GetConfigManager(_T("envvars"));
  if (!cfg)
  {
    set_names.Add(nsEnvVars::EnvVarsDefault);
    return set_names;
  }

  // Read all envvar sets available
  // 读取所有可用的环境变量集
  wxArrayString sets     = cfg->EnumerateSubPaths(_T("/sets"));
  unsigned int  num_sets = sets.GetCount();
  EV_DBGLOG(wxT("环境变量:  在配置中找到%u个环境变量集."), num_sets);

  if (num_sets==0)
    set_names.Add(nsEnvVars::EnvVarsDefault);
  else
  {
    for (unsigned int i=0; i<num_sets; ++i)
    {
      wxString set_name = sets[i];
      if (set_name.IsEmpty())
        set_name.Printf(_T("Set%u"), i);

      set_names.Add(set_name);
    }// for
  }// if

  return set_names;
}// GetEnvvarSetNames

// ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- -----

wxString nsEnvVars::GetActiveSetName()
{
#if defined(TRACE_ENVVARS)
  Manager::Get()->GetLogManager()->DebugLog(F(_T("GetActiveSetName")));
#endif

  wxString active_set = nsEnvVars::EnvVarsDefault;

  // load and apply configuration (to application only)
  // 加载和应用配置(仅适用于应用程序)
  ConfigManager *cfg = Manager::Get()->GetConfigManager(_T("envvars"));
  if (!cfg)
    return active_set;

  // try to get the envvar set name of the currently active global envvar set
  // 尝试获取当前活动全局环境变量集的环境变量集名称
  wxString active_set_cfg = cfg->Read(_T("/active_set"));
  if (!active_set_cfg.IsEmpty())
    active_set = active_set_cfg;

  EV_DBGLOG(wxT("环境变量:从配置中获取了作为活动环境变量集的'%s'."), active_set.wx_str());
  return active_set;
}// GetActiveSetName

// ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- -----

wxString nsEnvVars::GetSetPathByName(const wxString& set_name, bool check_exists,bool return_default)
{
#if defined(TRACE_ENVVARS)
  Manager::Get()->GetLogManager()->DebugLog(F(_T("GetSetPathByName")));
#endif
// fall back solution
// 回退解决方案
  wxString set_path = _T("/sets/")+nsEnvVars::EnvVarsDefault;
  if (!return_default)
    set_path.Empty();

  ConfigManager *cfg = Manager::Get()->GetConfigManager(_T("envvars"));
  if (!cfg || set_path.IsEmpty())
    return set_path;

  if (!check_exists)
    return _T("/sets/")+set_name;

  // Read all envvar sets available
  // 读取所有可用的环境变量集
  wxArrayString sets     = cfg->EnumerateSubPaths(_T("/sets"));
  unsigned int  num_sets = sets.GetCount();
  for (unsigned int i=0; i<num_sets; ++i)
  {
    if (set_name.IsSameAs(sets[i]))
    {
      set_path = (_T("/sets/")+set_name);
      // Early exit of for-loop
      // 提前退出for循环
      break;
    }
  }

  return set_path;
}// GetSetPathByName

// ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- -----

wxArrayString nsEnvVars::GetEnvvarsBySetPath(const wxString& set_path)
{
#if defined(TRACE_ENVVARS)
  Manager::Get()->GetLogManager()->DebugLog(F(_T("GetEnvvarsBySetPath")));
#endif

  wxArrayString envvars;
  EV_DBGLOG(wxT("环境变量:正在路径%s中搜索环境变量."), set_path.wx_str());

  ConfigManager *cfg = Manager::Get()->GetConfigManager(_T("envvars"));
  if (!cfg || set_path.IsEmpty())
    return envvars;

  wxArrayString envvars_keys = cfg->EnumerateKeys(set_path);
  unsigned int  num_envvars  = envvars_keys.GetCount();
  for (unsigned int i=0; i<num_envvars; ++i)
  {
    wxString envvar = cfg->Read(set_path+_T("/")+envvars_keys[i]);
    if (!envvar.IsEmpty())
      envvars.Add(envvar);
    else
      EV_DBGLOG(wxT("环境变量: 警告:检测到并跳过空环境变量."));
  }
  EV_DBGLOG(wxT("环境变量:  读取路径'%s'中的%lu/%u环境变量."),set_path.wx_str(),static_cast<unsigned long>(envvars.GetCount()), num_envvars);

  return envvars;
}// GetEnvvarsBySetPath

// ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- -----

bool nsEnvVars::EnvvarSetExists(const wxString& set_name)
{
#if defined(TRACE_ENVVARS)
  Manager::Get()->GetLogManager()->DebugLog(F(_T("EnvvarSetExists")));
#endif

  if (set_name.IsEmpty())
    return false;

  wxString set_path = nsEnvVars::GetSetPathByName(set_name, true, false);
  if (set_name.IsEmpty())
    return false;

  return true;
}// EnvvarSetExists

// ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- -----

bool nsEnvVars::EnvvarVetoUI(const wxString& key, wxCheckListBox* lstEnvVars, int sel)
{
#if defined(TRACE_ENVVARS)
  Manager::Get()->GetLogManager()->DebugLog(F(_T("EnvvarVetoUI")));
#endif

  if (wxGetEnv(key, NULL))
  {
    wxString recursion;
    if (platform::windows) recursion = _T("PATH=%PATH%;C:\\NewPath");
    else                   recursion = _T("PATH=$PATH:/new_path");

    wxString warn_exist;
    warn_exist.Printf(wxT("警告：环境变量'%s'已设置.是否继续更新其值?(将考虑类似于'%s'的递归.)"),
                        key.wx_str(), recursion.wx_str());

    if (cbMessageBox(warn_exist, wxT("确认"),
                     wxYES_NO | wxICON_QUESTION) == wxID_NO)
    {
        // Unset to visualise it's NOT set
        // 未设置为可视化它未设置
      if (lstEnvVars && (sel>=0))
        lstEnvVars->Check(sel, false);
      // User has vetoed the operation
      // 用户已否决该操作
      return true;
    }
  }// if

  return false;
}// EnvvarVetoUI

// ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- -----

bool nsEnvVars::EnvvarsClearUI(wxCheckListBox* lstEnvVars)
{
#if defined(TRACE_ENVVARS)
  Manager::Get()->GetLogManager()->DebugLog(F(_T("EnvvarsClearUI")));
#endif

  if (!lstEnvVars) return false;

  wxString envsNotUnSet(wxEmptyString);

  // Unset all (checked) variables of lstEnvVars
  // 取消设置lstEnvVars的所有(选中)变量
  for (int i=0; i<(int)lstEnvVars->GetCount(); ++i)
  {
    // Note: It's better not to just clear all because wxUnsetEnv would
    //       fail in case an envvar is not set (not checked).
    // 注:最好不要全部清除,因为如果未设置环境变量(未选中),wxUnsetEnv将失败.
    if (lstEnvVars->IsChecked(i))
    {
      wxString key = lstEnvVars->GetString(i).BeforeFirst(_T('=')).Trim(true).Trim(false);
      if (!key.IsEmpty())
      {
        if (!nsEnvVars::EnvvarDiscard(key))
        {
          // Setting env.-variable failed. Remember this key to report later.
          // 设置环境变量失败.记住这个键,以后再报告.
          if (envsNotUnSet.IsEmpty())
            envsNotUnSet << key;
          else
            envsNotUnSet << _T(", ") << key;
        }
      }
    }
  }// for

  lstEnvVars->Clear();

  if (!envsNotUnSet.IsEmpty())
  {
    wxString msg;
    msg.Printf( wxT("设置以下环境变量时出错:%s"),
                envsNotUnSet.wx_str() );
    cbMessageBox(msg,wxT("错误"), wxOK | wxCENTRE | wxICON_ERROR);
    return false;
  }

  return true;
}// EnvvarsClearUI

// ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- -----

bool nsEnvVars::EnvvarIsRecursive(const wxString& key, const wxString& value)
{
  // Replace all macros the user might have setup for the key
  // 替换用户为键设置的所有宏
  wxString the_key = key;
  Manager::Get()->GetMacrosManager()->ReplaceMacros(the_key);

  wxString recursion;
  if (platform::windows) recursion = _T("%")+the_key+_("%");
  else                   recursion = _T("$")+the_key;

  if (value.Contains(recursion))
    return true;

  return false;
}// EnvvarIsRecursive

// ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- -----

bool nsEnvVars::EnvvarDiscard(const wxString &key)
{
#if defined(TRACE_ENVVARS)
  Manager::Get()->GetLogManager()->DebugLog(F(_T("EnvvarDiscard")));
#endif

  // Replace all macros the user might have setup for the key
  // 替换用户为键设置的所有宏
  wxString the_key = key;
  Manager::Get()->GetMacrosManager()->ReplaceMacros(the_key);
  if (the_key.Trim().IsEmpty()) return false;
  // envvar was not set - nothing to do.
  // 环境变量未设置-无事可做.
  if (!wxGetEnv(the_key, NULL)) return false;

  std::map<wxString,wxString>::iterator it = nsEnvVars::EnvVarsStack.find(the_key);
  // found an old envvar on the stack
  // 在堆栈上找到一个旧的环境变量
  if (it!=nsEnvVars::EnvVarsStack.end()){
      // restore old value
      // 恢复旧值
      return nsEnvVars::EnvvarApply(the_key, it->second);
  }

  if (!wxUnsetEnv(the_key))
  {
    Manager::Get()->GetLogManager()->Log(F(
      wxT("设置环境变量'%s'失败."), the_key.wx_str())
    );
    EV_DBGLOG(wxT("环境变量: 撤销设置环境变量'%s'失败."), the_key.wx_str());
    return false;
  }

  return true;
}// EnvvarDiscard

// ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- -----

bool nsEnvVars::EnvvarApply(const wxString& key, const wxString& value)
{
#if defined(TRACE_ENVVARS)
  Manager::Get()->GetLogManager()->DebugLog(F(_T("EnvvarApply")));
#endif

  // Replace all macros the user might have setup for the key
  // 替换用户为键设置的所有宏
  wxString the_key = key;
  Manager::Get()->GetMacrosManager()->ReplaceMacros(the_key);
  if (the_key.Trim().IsEmpty()) return false;

  // Value: First, expand stuff like:
  //        set PATH=%PATH%;C:\NewPath OR export PATH=$PATH:/new_path
  //        After, replace all macros the user might have used in addition
  // 值:首先,展开如下内容:
  // set PATH=%PATH%;C:\NewPath OR export PATH=$PATH:/new_path
  // 之后,替换用户可能另外使用的所有宏
  wxString value_set;
  bool     is_set    = wxGetEnv(the_key, &value_set);
  wxString the_value = value;
  if (is_set)
  {
    std::map<wxString,wxString>::iterator it = nsEnvVars::EnvVarsStack.find(the_key);
    // envvar not already on the stack
    // 环境变量不在堆栈上
    if (it==nsEnvVars::EnvVarsStack.end()){
        // remember the old value
        // 记住旧的值
        nsEnvVars::EnvVarsStack[the_key] = value_set;
    }

    // Avoid endless recursion if the value set contains e.g. $PATH, too
    // 如果值集也包含$PATH,则避免无休止的递归
    if (nsEnvVars::EnvvarIsRecursive(the_key,the_value))
    {
      if (nsEnvVars::EnvvarIsRecursive(the_key,value_set))
      {
        EV_DBGLOG(wxT("环境变量:  由于无法解析递归,设置环境变量'%s'失败."), the_key.wx_str());
        return false;
      }
      // Restore original value in case of recursion before
      // 递归之前恢复原值
      if (it!=nsEnvVars::EnvVarsStack.end())
        value_set = nsEnvVars::EnvVarsStack[the_key];
      // Resolve recursion now (if any)
      // 立即解析递归(如果有)
      wxString recursion;
      if (platform::windows) recursion = _T("%")+the_key+_("%");
      else                   recursion = _T("$")+the_key;
      the_value.Replace(recursion.wx_str(), value_set.wx_str());
    }
  }
  // Replace all macros the user might have setup for the value
  // 替换用户为该值设置的所有宏
  Manager::Get()->GetMacrosManager()->ReplaceMacros(the_value);

  EV_DBGLOG(wxT("环境变量: 正在尝试将环境变量%s设置为值%s..."), the_key.wx_str(), the_value.wx_str());
  // set the envvar as computed
  // 将环境变量设置为计算值
  if (!wxSetEnv(the_key, the_value))
  {
    EV_DBGLOG(wxT("环境变量:  设置环境变量'%s'失败."), the_key.wx_str());
    return false;
  }

  return true;
}// EnvvarApply

// ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- -----

bool nsEnvVars::EnvvarArrayApply(const wxArrayString& envvar,
                                 wxCheckListBox*      lstEnvVars)
{
#if defined(TRACE_ENVVARS)
  Manager::Get()->GetLogManager()->DebugLog(F(_T("EnvvarArrayApply")));
#endif

  if (envvar.GetCount() == 3)
  {
    wxString check = envvar[0];
    wxString key   = envvar[1];
    wxString value = envvar[2];

    bool bCheck = check.Trim(true).Trim(false).IsSameAs(_T("1"))?true:false;
    key.Trim(true).Trim(false);
    value.Trim(true).Trim(false);

    int sel = -1;
    if (lstEnvVars)
    {
#if CHECK_LIST_BOX_CLIENT_DATA==1
      sel = lstEnvVars->Append(key + _T(" = ") + value, new EnvVariableListClientData(key, value));
#else
      sel = lstEnvVars->Append(key + _T(" = ") + value);
#endif
      lstEnvVars->Check(sel, bCheck);
    }

    if (bCheck)
    {
      bool success = EnvvarApply(key, value);
      // Unset on UI to mark it's NOT set
      if (!success && lstEnvVars && sel>=0)
        lstEnvVars->Check(sel, false);

      return success;
    }else{
        // No need to apply -> success, too.
        // 不需要申请->成功
        return true;
    }
  }// if

  return false;
}// EnvvarArrayApply

// ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- -----

void nsEnvVars::EnvvarSetApply(const wxString& set_name, bool even_if_active)
{
#if defined(TRACE_ENVVARS)
  Manager::Get()->GetLogManager()->DebugLog(F(_T("EnvvarSetApply")));
#endif

  // Load and apply envvar set from config (to application only)
  // 从配置加载并应用环境变量集(仅适用于应用程序)
  ConfigManager *cfg = Manager::Get()->GetConfigManager(_T("envvars"));
  if (!cfg)
    return;

  // Stores the currently active envar set that has been successfully applied at last
  // 存储最后成功应用的当前活动环境变量集
  static wxString last_set_applied = wxEmptyString;

  wxString set_to_apply = set_name;
  if (set_to_apply.IsEmpty())
    set_to_apply = nsEnvVars::GetActiveSetName();

  // Early exit for a special case requested by even_if_active parameter
  // even_if_active参数请求的特殊情况的提前退出
  if (!even_if_active && set_to_apply.IsSameAs(last_set_applied))
  {
    EV_DBGLOG(wxT("环境变量: 将不应用集合'%s'(已处于活动状态)."),set_to_apply.wx_str());
    return;
  }

  // Show currently activated set in debug log (for reference)
  // 在调试日志中显示当前激活的集(供参考)
  wxString set_path = nsEnvVars::GetSetPathByName(set_to_apply);
  EV_DBGLOG(wxT("环境变量: 活动环境变量集是'%s',配置路径'%s'."),set_to_apply.wx_str(), set_path.wx_str());

  // NOTE: Keep this in sync with EnvVarsConfigDlg::LoadSettings
  // Read and apply all envvars from currently active set in config
  // 注:保持与EnvVarsConfigDlg::LoadSettings同步
  // 读取并应用配置中当前活动集的所有环境变量
  wxArrayString vars     = nsEnvVars::GetEnvvarsBySetPath(set_path);
  size_t envvars_total   = vars.GetCount();
  size_t envvars_applied = 0;
  for (unsigned int i=0; i<envvars_total; ++i)
  {
    // Format: [checked?]|[key]|[value]
    wxArrayString var_array = nsEnvVars::EnvvarStringTokeniser(vars[i]);
    if (nsEnvVars::EnvvarArrayApply(var_array))
      envvars_applied++;
    else
    {
      EV_DBGLOG(wxT("环境变量: %s中位置#%u处的环境变量无效."),
        set_path.wx_str(), i);
    }
  }// for

  if (envvars_total>0)
  {
    last_set_applied = set_to_apply;
    EV_DBGLOG(wxT("环境变量:  %lu/%lu环境变量应用于C::B焦点."),
      static_cast<unsigned long>(envvars_applied), static_cast<unsigned long>(envvars_total));
  }
}// EnvvarSetApply

// ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- -----

void nsEnvVars::EnvvarSetDiscard(const wxString& set_name)
{
#if defined(TRACE_ENVVARS)
  Manager::Get()->GetLogManager()->DebugLog(F(_T("EnvvarSetDiscard")));
#endif

  // load and apply envvar set from config (to application only)
  ConfigManager *cfg = Manager::Get()->GetConfigManager(_T("envvars"));
  if (!cfg)
    return;

  wxString set_to_discard = set_name;
  if (set_to_discard.IsEmpty())
    set_to_discard = nsEnvVars::GetActiveSetName();

  // Show currently activated set in debug log (for reference)
  wxString set_path = nsEnvVars::GetSetPathByName(set_to_discard);
  EV_DBGLOG(wxT("环境变量: 活动环境变量集为%s,配置路径为%s."),
    set_to_discard.wx_str(), set_path.wx_str());

  // Read and apply all envvars from currently active set in config
  // 读取并应用配置中当前活动集的所有环境变量
  wxArrayString vars       = nsEnvVars::GetEnvvarsBySetPath(set_path);
  size_t envvars_total     = vars.GetCount();
  size_t envvars_discarded = 0;
  for (unsigned int i=0; i<envvars_total; ++i)
  {
    // Format: [checked?]|[key]|[value]
    wxArrayString var_array = nsEnvVars::EnvvarStringTokeniser(vars[i]);
    if (var_array.GetCount()==3)
    {
      wxString check = var_array[0];
      bool bCheck = check.Trim(true).Trim(false).IsSameAs(_T("1"))?true:false;
      // Do not unset envvars that are not activated (checked)
      // 不要取消设置未激活的环境变量(选中)
      if (!bCheck) {continue;}

      // unset the old envvar
      // 取消设置旧环境变量
      if (nsEnvVars::EnvvarDiscard(var_array[1]))
        envvars_discarded++;
    }
    else
    {
      EV_DBGLOG(wxT("环境变量: %s中位置#%u处的环境变量无效."),
        set_path.wx_str(), i);
    }
  }// for

  if (envvars_total>0)
  {
    EV_DBGLOG(wxT("环境变量:  %lu/%lu环境变量在C::B焦点内被丢弃."),
      static_cast<unsigned long>(envvars_discarded), static_cast<unsigned long>(envvars_total));
  }
}// EnvvarSetDiscard
